import type { Rule as ValidationRule } from "antd/lib/form";
import { Col, Divider, Form } from "antd";
import { cloneDeep, upperFirst } from "lodash-es";
import type { FormProps, FormSchemaInner as FormSchema } from "../types/form";
import { isComponentFormSchema } from "../types/form";
import { componentMap } from "../componentMap";
import { NO_AUTO_LINK_COMPONENTS, createPlaceholderMessage, setComponentRuleType } from "../helper";
import { useItemLabelWidth } from "../hooks/useLabelWidth";
// import { BasicHelp } from "@/components/Basic";
import { isBoolean, isFunction, isNull } from "@/utils/is";
import { getSlot } from "@/utils/helper/tsxHelper";
import type { Nullable, Recordable } from "@/utils/types";
import React, { useMemo } from "react";
import { ItemPropsType, ItemPropsDefault } from "../props";
import { useTranslation } from "react-i18next";

interface ComponentProps {
	slots?: SlotsComponent;
	// controlMap: Record<string, string[]>;
	formModel: Recordable<any>;
	setFormModel: (key: string, value: any, schema: FormSchema) => void;
}

const FormItem = (props: ItemPropsType & ComponentProps) => {
	// fix React18 "defaultProps" problem
	props = Object.assign({}, { ...ItemPropsDefault, ...props });

	const { t } = useTranslation();
	const slots = props.slots;

	const itemLabelWidthProp = useItemLabelWidth(props.schema, props.formProps);
	const getValues = useMemo(() => {
		const { initialValues, formModel, schema } = props;
		const { mergeDynamicData } = props.formProps;
		return {
			field: schema.field,
			values: {
				...mergeDynamicData,
				...initialValues,
				...formModel
			} as Recordable<any>,
			schema
		};
	}, [props.initialValues, props.schema, props.formProps?.mergeDynamicData, props.formModel]);

	const getComponentsProps = useMemo(() => {
		const { schema, tableAction, formActionType } = props;
		let { componentProps = {} } = schema as FormSchema;
		if (isFunction(componentProps)) componentProps = componentProps({ schema, tableAction, formActionType }) ?? {};

		if (schema.component === "Divider") {
			componentProps = Object.assign(
				{ type: "horizontal" },
				{
					orientation: "left",
					plain: true
				},
				componentProps
			);
		}
		return componentProps as Recordable<any>;
	}, [props.schema, props.tableAction, props.formActionType]);

	const getDisable = useMemo(() => {
		const { disabled: globDisabled } = props.formProps;
		const { dynamicDisabled } = props.schema;
		const { disabled: itemDisabled = false } = getComponentsProps;
		let disabled = !!globDisabled || itemDisabled;
		if (isBoolean(dynamicDisabled)) disabled = dynamicDisabled;

		if (isFunction(dynamicDisabled)) disabled = dynamicDisabled(getValues);

		return disabled;
	}, [props.formProps?.disabled, props.schema?.dynamicDisabled, getComponentsProps]);

	function getShow(): { isShow: boolean; isIfShow: boolean } {
		const { show, ifShow } = props.schema;
		const { showAdvancedButton } = props.formProps;
		const itemIsAdvanced = showAdvancedButton ? (isBoolean(props.isAdvanced) ? props.isAdvanced : true) : true;

		let isShow = true;
		let isIfShow = true;

		if (isBoolean(show)) isShow = show;

		if (isBoolean(ifShow)) isIfShow = ifShow;

		if (isFunction(show)) isShow = show(getValues);

		if (isFunction(ifShow)) isIfShow = ifShow(getValues);

		isShow = isShow && itemIsAdvanced;
		return { isShow, isIfShow };
	}

	function handleRules(): ValidationRule[] {
		const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required } = props.schema;

		if (isFunction(dynamicRules)) return dynamicRules(getValues) as ValidationRule[];

		let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
		const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;

		const joinLabel = Reflect.has(props.schema, "rulesMessageJoinLabel") ? rulesMessageJoinLabel : globalRulesMessageJoinLabel;
		const assertLabel = joinLabel ? label : "";
		const defaultMsg = component ? createPlaceholderMessage(component, t) + " " + assertLabel : assertLabel;

		function validator(rule: any, value: any) {
			const msg = rule.message || defaultMsg;
			if (value === undefined || isNull(value)) {
				// 空值
				return Promise.reject(msg);
			} else if (Array.isArray(value) && value.length === 0) {
				// 数组类型
				return Promise.reject(msg);
			} else if (typeof value === "string" && value.trim() === "") {
				// 空字符串
				return Promise.reject(msg);
			} else if (
				typeof value === "object" &&
				Reflect.has(value, "checked") &&
				Reflect.has(value, "halfChecked") &&
				Array.isArray(value.checked) &&
				Array.isArray(value.halfChecked) &&
				value.checked.length === 0 &&
				value.halfChecked.length === 0
			) {
				// 非关联选择的tree组件
				return Promise.reject(msg);
			}
			return Promise.resolve();
		}

		const getRequired = isFunction(required) ? required(getValues) : required;

		/*
		 * 1、若设置了required属性，又没有其他的rules，就创建一个验证规则；
		 * 2、若设置了required属性，又存在其他的rules，则只rules中不存在required属性时，才添加验证required的规则
		 *     也就是说rules中的required，优先级大于required
		 */
		if (getRequired) {
			if (!rules || rules.length === 0) {
				rules = [{ required: getRequired, validator }];
			} else {
				const requiredIndex: number = rules.findIndex(rule => Reflect.has(rule, "required"));

				if (requiredIndex === -1) rules.push({ required: getRequired, validator });
			}
		}

		const requiredRuleIndex: number = rules.findIndex(rule => Reflect.has(rule, "required") && !Reflect.has(rule, "validator"));

		if (requiredRuleIndex !== -1) {
			const rule = rules[requiredRuleIndex];
			const { isShow } = getShow();
			if (!isShow) rule.required = false;

			if (component) {
				rule.message = rule.message || defaultMsg;

				if (component.includes("Input") || component.includes("Textarea")) rule.whitespace = true;

				const valueFormat = getComponentsProps?.valueFormat;
				setComponentRuleType(rule, component, valueFormat);
			}
		}

		// Maximum input length rule check
		const characterInx = rules.findIndex(val => val.max);
		if (characterInx !== -1 && !rules[characterInx].validator) {
			rules[characterInx].message = rules[characterInx].message || t("component.form.maxTip") + rules[characterInx].max;
		}
		return rules;
	}

	function renderComponent() {
		if (!isComponentFormSchema(props.schema)) return null;

		const { renderComponentContent, component, field, changeEvent = "change" } = props.schema;
		const { setFieldValue } = props.formActionType;

		const isCheck = component && ["Switch", "Checkbox", "CheckboxGroup"].includes(component);
		const isCustomComponent = ["FileUpload"].includes(component);

		const { autoSetPlaceHolder, size } = props.formProps;
		const propsData: Recordable<any> = {
			size,
			...getComponentsProps,
			disabled: getDisable
		};

		// Do not provide that the React Dom does not support.
		if (!["Checkbox", "CheckboxGroup", "RadioGroup"].includes(component)) {
			propsData["allowClear"] = true;
		}

		const eventKey = `on${upperFirst(changeEvent)}`;
		const on = {
			[eventKey]: (...args: Nullable<Recordable<any>>[]) => {
				const [e] = args;
				if (propsData[eventKey]) propsData[eventKey](...args);

				const target = e ? e.target : null;
				const value = target ? (isCheck ? target.checked : target.value) : e;
				// 自定义组件需要自行赋值
				if (isCustomComponent) setFieldValue(field, value);
				// 修改Form中props导致form重新渲染,否则ifShow无法动态生效
				props.setFormModel(field, value, props.schema);
			}
		};

		const Comp = componentMap.get(component) as unknown as ReturnType<React.FC>;

		const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
		// RangePicker place is an array
		if (isCreatePlaceholder && component !== "RangePicker" && component) {
			propsData.placeholder = getComponentsProps?.placeholder || createPlaceholderMessage(component, t);
		}

		let compAttr: Recordable<any> = {
			...propsData,
			...on
		};

		if (!renderComponentContent) return <Comp {...compAttr} />;

		const compSlot = isFunction(renderComponentContent)
			? { ...renderComponentContent(getValues, { disabled: getDisable }) }
			: {
					default: () => renderComponentContent
			  };
		return <Comp {...compAttr}>{compSlot}</Comp>;
	}

	function renderLabelHelpMessage() {
		const { label, helpMessage, subLabel } = props.schema;
		const renderLabel = subLabel ? (
			<span>
				{label} <span className="text-secondary">{subLabel}</span>
			</span>
		) : (
			label
		);
		const getHelpMessage = isFunction(helpMessage) ? helpMessage(getValues) : helpMessage;
		if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) return renderLabel;

		return (
			<span>
				{renderLabel}
				{/* <BasicHelp placement="top" className="mx-1" text={getHelpMessage} {...helpComponentProps} /> */}
			</span>
		);
	}

	function renderItem() {
		const { itemProps, slot, render, field, suffix, component } = props.schema;
		const { labelCol, wrapperCol } = itemLabelWidthProp;
		const { colon } = props.formProps;
		const opts = { disabled: getDisable };

		if (component === "Divider") {
			return (
				<Col span={24}>
					<Divider {...getComponentsProps}>{renderLabelHelpMessage()}</Divider>
				</Col>
			);
		} else {
			const getContent = () => {
				return slot ? getSlot(slots, slot) : render ? render(getValues, opts) : renderComponent();
			};

			const showSuffix = !!suffix;
			// const getSuffix = isFunction(suffix) ? suffix(getValues) : suffix;

			// TODO 自定义组件验证会出现问题，因此这里框架默认将自定义组件设置手动触发验证，如果其他组件还有此问题请手动设置autoLink=false
			if (component && NO_AUTO_LINK_COMPONENTS.includes(component)) {
				props.schema &&
					(props.schema.itemProps! = {
						autoLink: false,
						...props.schema.itemProps
					});
			}

			return (
				<Form.Item
					name={field}
					colon={colon}
					className={showSuffix ? "suffix-item" : ""}
					{...(itemProps as Recordable<any>)}
					label={renderLabelHelpMessage()}
					rules={handleRules()}
					labelCol={labelCol}
					wrapperCol={wrapperCol}
				>
					{getContent()}
				</Form.Item>
				/**@explainBy:Jok-2025/01/25=16:24-Form.Item下无法嵌套div，否则数据劫持失效（后缀暂时无法使用）*/
				// {showSuffix && <div className="suffix">{getSuffix}</div>}
			);
		}
	}

	const { colProps = {}, colSlot, renderColContent, component, slot } = props.schema as FormSchema;
	if (!component || (!componentMap.has(component) && !slot)) return null;

	const { baseColProps = {} } = props.formProps as FormProps;
	const realColProps = { ...baseColProps, ...colProps };
	const { isIfShow, isShow } = getShow();
	const values = getValues;
	const opts = { disabled: getDisable };

	const getContent = () => {
		return colSlot ? getSlot(slots, colSlot) : renderColContent ? renderColContent(values, opts) : renderItem();
	};
	return isIfShow && isShow ? <Col {...realColProps}>{getContent()}</Col> : null;
};

export default FormItem;
